#!/usr/bin/env python3
#coding:utf-8

__author__ = 'xmxoxo<xmxoxo@qq.com>'

'''
依赖库： dlib
'''

import json
import os
import re
import sys
import time
import requests
import numpy as np
import cv2
import base64
from minRect import *
from PIL import Image, ImageDraw, ImageFont

pl = lambda x='', y='-': print(y*40) if x=='' else print(x) if x[-3:]=='...' else print(x.center(40, y))

# 读入文件
def readtxt(fname, encoding='utf-8'):
    try:
        with open(fname, 'r', encoding=encoding) as f:
            data = f.read()
        return data
    except Exception as e:
        return ''

# 保存文本信息到文件
def savetofile(txt, filename, encoding='utf-8', method='w'):
    pass
    try:
        with open(filename, method, encoding=encoding) as f:
            f.write(txt)
        return 1
    except Exception as e:
        print(e)
        return 0

# 创建目录
def mkfold(new_dir):
    if not os.path.exists(new_dir):
        os.makedirs(new_dir)


# 单条文本提取NER
def get_ner (text, url='http://192.168.15.111:8920/query'):
    try:
        pers = ''
        res = requests.post(url, json={"text": text}, timeout=3)
        res.encoding = 'utf-8'
        #print(res.text)
        res = json.loads(res.text)
        if res['result']:
            pers = res["result"].get('PER',None)
        return pers
    except Exception as e:
        print(e)
        return None

'''
# 获得所有子目录或者文件（只处理1级子目录）
folder： 获取子目录，1=只返回子目录名 0=只返回所有文件名
# 返回结果：(file_path, dirname)
    其中： file_path为完整路径名；dirname为文件或者目录名
'''
def get_files(path, folder=1):
    for dirname in os.listdir(path):
        file_path = os.path.join(path, dirname)
        if folder:
            if os.path.isdir(file_path):
                yield file_path, dirname
        else:
            if os.path.isfile(file_path):
                file_ext = os.path.splitext(file_path)[1].lower()
                if file_ext in ['.jpg','.png', '.jpeg']:
                    yield file_path, dirname
# -----------------------------------------
# 加载检测器
def load_detection_model(modelfile='face_models/shape_predictor_68_face_landmarks.dat' ):
    import dlib
    # 使用 Dlib 的正面人脸检测器 frontal_face_detector
    detector = dlib.get_frontal_face_detector()
    predictor = dlib.shape_predictor(modelfile)
    return detector, predictor


'''
# 图像人脸检测
saveface: 是否保存
pre_name：保存的文件名前缀
outpath： 保存的目录
返回: LIST, 提取的人脸图像数组；
'''
def face_detector(filename, detector, predictor,
                    showwin=1, saveface=1,
                    pre_name='image_cap', outpath='./'):
    ret = []
    image = None
    if type(filename) == str:
        # 改为适应中文文件名路径
        # img = cv2.imread(filename)
        image = cv2.imdecode(np.fromfile(filename, dtype=np.uint8), -1)
    if type(filename) == bytes:
        image = cv2.imdecode(np.frombuffer(filename, np.uint8), cv2.IMREAD_COLOR)
    if type(filename) == np.ndarray:
        image = filename

    # 加载错误时退出
    if image is None:
        return ret, None

    #print('image:', type(image))
    source_image = image.copy()
    try:
        img = cv2.cvtColor(src=image, code=cv2.COLOR_BGR2GRAY)
    except Exception as e:
        img = image.copy()

    # 使用 detector 检测器来检测图像中的人脸
    # use detector of Dlib to detector faces
    #faces = detector(img, 1)
    faces = detector(img)
    if showwin:
        print("人脸数 / Faces in all: ", len(faces))

    # Traversal every face
    for i, d in enumerate(faces):
        landmarks = predictor(image=img, box=d)
        #print('landmarks:',landmarks, type(landmarks))
        #print('landmarks.part:', landmarks.part(0).tolist(), type(landmarks.part(0)) )
        # 从识别的人脸d中获得人脸特征点
        # landmarks = np.matrix([[p.x, p.y] for p in detected_landmarks])

        # cut sub image: contours就是特征点矩阵
        contours = np.array([[landmarks.part(n).x,landmarks.part(n).y] for n in range(68)])
        cut_img = cut_points(source_image, contours)
        ret.append(cut_img)
        #print(type(cut_img))
        if saveface :    #保存人脸图像  and cut_img.size!=0
            try:
                out_img_filename = os.path.join(outpath, '%s_%d.png' % (pre_name, i))
                #cv2.imwrite(out_img_filename, cut_img)
                cv2.imencode('.jpg', cut_img)[1].tofile(out_img_filename) #英文或中文路径均适用
                # 保存特征向量
                out_npy_filename = os.path.join(outpath, '%s_%d.npy' % (pre_name, i))
                np.save(out_npy_filename, contours)
                #print('已保存')
            except Exception as e:
                # print(e)
                #print('save image error')
                pass

        if showwin:
            cv2.imshow("cut_img_%d"%i, cut_img)

        print('第%d个人脸的矩形框坐标：left:%d right:%d top:%d bottom:%d ' % (
            i+1, d.left(), d.right(), d.top(),d.bottom()))

        cv2.rectangle(image, tuple([d.left(), d.top()]), tuple([d.right(), d.bottom()]), (255, 0, 255), 2)

        # Loop through all the points 画出人脸标志点
        for n in range(0, 68):
            x = landmarks.part(n).x
            y = landmarks.part(n).y
            # Draw a circle
            cv2.circle(img=image, center=(x, y), radius=2, color=(0, 255, 0), thickness=1)

    if showwin:
        cv2.namedWindow("img")
        cv2.imshow("img", image)
        #cv2.imwrite('cap.png',image)
        cv2.waitKey(0)

    # ret = np.array(ret) 直接转array可能会出现大小不一的情况, 所以直接返回列表
    # 同时返回标识后的画面
    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
    return ret, image

# 提取人脸特征
def face_feature(filename, detector, predictor,
                    showwin=1, saveface=1,
                    pre_name='image_cap', outpath='./'):
    ''' 提取图像中的人脸特征，并保存为向量文件
    '''

    ret = []
    image = cv2.imdecode(np.fromfile(filename,dtype=np.uint8), -1)
    # 加载错误时退出
    if image is None:
        return ret
    source_image = image.copy()
    try:
        img = cv2.cvtColor(src=image, code=cv2.COLOR_BGR2GRAY)
    except Exception as e:
        img = image.copy()

    # 使用 detector 检测器来检测图像中的人脸
    faces = detector(img, 1)
    if showwin:
        print("人脸数 / Faces in all: ", len(faces))

    # Traversal every face
    for i, d in enumerate(faces):
        landmarks = predictor(image=img, box=d)
        #print('landmarks:',landmarks, type(landmarks))
        #print('landmarks.part:', landmarks.part(0).tolist(), type(landmarks.part(0)) )
        # 从识别的人脸d中获得人脸特征点
        # landmarks = np.matrix([[p.x, p.y] for p in detected_landmarks])

        # cut sub image contours就是特征点矩阵
        contours = np.array([[landmarks.part(n).x,landmarks.part(n).y] for n in range(68)])
        cut_img = cut_points(source_image, contours)
        ret.append(cut_img)
        #print(type(cut_img))
        try:
            # 保存人脸特征向量
            out_npy_filename = os.path.join(outpath, '%s_%d.npy' % (pre_name, i))
            np.save(out_npy_filename, contours)
        except Exception as e:
            pass

# -----------------------------------------
# 加载图像，转换成224x224大小，再转成np.array输出
def load_images(imageslist):
    required_size = (224, 224)
    image_array = []
    for dat in imageslist:
        if type(dat) == str:
            image = Image.open(dat, 'r')
        if type(dat) == list:
            image = Image.fromarray(np.array(dat))
        if type(dat) == np.ndarray:
            image = Image.fromarray(dat)

        image = image.resize(required_size)
        face_image = np.asarray(image, 'float32')
        image_array.append(face_image)

    image_array = np.array(image_array)

    return image_array

'''
提取图像特征向量，输出向量大小：2048
'''
def get_model_scores(model, samples):
    from keras_vggface.utils import preprocess_input

    # 图像预处理
    samples = preprocess_input(samples, version=2)
    # print('samples:', samples.shape)

    vector = model.predict(samples)
    return vector

def load_vggface_model():
    from keras_vggface.vggface import VGGFace
    model = VGGFace(model='resnet50',
              include_top=False,
              input_shape=(224, 224, 3),
              pooling='avg')
    return model

# 显示图片文件
def show_image(fname, winname='image'):
    image = Image.open(fname)
    img = cv2.cvtColor(np.asarray(image), cv2.COLOR_RGB2BGR)
    cv2.imshow(winname, img)
    # cv2.waitKey(0)

def test_get_files():
    '''单元测试 获取子目录所有文件
    '''

    path = './test_images'
    result = get_files(path, folder=0)
    print('files:\n', list(result))

    print('-'*40)
    result = get_files('./', folder=1)
    print('folders:\n', list(result))

# -----------------------------------------
# 图像格式之间的转换：文件, bytes, BytesIO, PIL.Image, np.array, base64

def get_base64(src):
    ''' 文件==》base4
    '''
    with open(src, 'rb') as f:
        base64_data = base64.b64encode(f.read())
        s = base64_data.decode()
        return s

def get_base64_stream(stream):
    base64_data = base64.b64encode(stream)
    s = base64_data.decode()
    return s

def array2base64(image_array):
    ''' np.ndarray ==>base64
    '''
    success, encoded_image = cv2.imencode(".jpg", image_array)
    # 对数组的图片格式进行编码
    bytes = encoded_image.tostring()
    # print('bytes:', type(bytes), len(bytes))
    b64 = get_base64_stream(bytes)
    return b64

def base64toarray(image_b64):
    ''' base64 ==> np.ndarray
    '''
    # 解码
    bytes = base64.b64decode(image_b64)
    # print('bytes:', type(bytes), len(bytes))
    # 转 ndarray
    ret = cv2.imdecode(np.frombuffer(bytes, np.uint8), cv2.IMREAD_COLOR)
    image = cv2.cvtColor(ret, cv2.COLOR_BGR2RGB)
    return image

class IMGBase64():
    ''' 图像base64编码类
    '''

    def __init__(self):
        self.filename = ''
        self.b64 = ''
        self.ndarray = None

    def set_filename(self, filename):
        ''' 设置图像文件名
        '''

        self.filename = filename
        self.b64 = get_base64(filename)
        self.ndarray = None

    def set_array(self, image_array):
        ''' 设置图像array数据
        '''

        self.ndarray = image_array
        self.b64 = array2base64(image_array)
        self.filename = ''

    def get_array(self):
        ''' 返回array
        '''
        if self.ndarray is None:
            if self.b64 != '':
                image = base64toarray(self.b64)
                self.ndarray = image
        return self.ndarray

    def get_base64(self):
        ''' 返回base64
        '''
        return self.b64
# -----------------------------------------

def cv2ImgAddText(img, text, left, top, textColor=(0, 255, 0), textSize=20):
    '''在图片中添加文字
    '''
    if (isinstance(img, np.ndarray)):  #判断是否OpenCV图片类型
        img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
    draw = ImageDraw.Draw(img)
    # fontStyle = ImageFont.truetype('NotoSansCJK-Bold.ttc', textSize, encoding="utf-8")
    fontStyle = ImageFont.truetype("font/simsun.ttc", textSize, encoding="utf-8")
    draw.text((left, top), text, textColor, font=fontStyle)
    return cv2.cvtColor(np.asarray(img), cv2.COLOR_RGB2BGR)

def show_result(source:list, results:list, titles:list, showimg=1):
    ''' 把多个图合并到一个图像中进行展示
    把搜索结果合并到一个图像中进行展示，高度为：200图像+50标题
    布局为：原图，结果图,结果图....; 每个图片下方显示标题文本
    参数：
        原图，1张图
        结果图，多个图
        每个结果图对应的标题文本；
    测试用例：
    results = [
                '夏美酱_柳侑绮/image_427_038_0.png',
                '夏美酱_柳侑绮/image_427_009_1.png',
                '夏美酱_柳侑绮/image_427_035_0.png',
                '夏美酱_柳侑绮/image_427_002_1.png',
                '夏美酱_柳侑绮/image_427_042_0.png',
                ]
    titles = [
                '0.95,夏美酱_柳侑绮',
                '0.93,夏美酱_柳侑绮',
                '0.92,夏美酱_柳侑绮',
                '0.83,夏美酱_柳侑绮',
                '0.81,夏美酱_柳侑绮',
                ]
    '''

    # 小图大小：
    w,h = 150,150
    # 留白
    mar = 10
    fsize = 12
    font_color = (0,0,255)
    # 计算结果数量
    n = len(results)
    # 创建空画布: H,w = 250, (n+1)*200
    # 画白底留黑框
    retpic = np.zeros((150+mar*4, (n+1)*(w + mar*2), 3), np.uint8)
    retpic[3:-3, 3:-3] = 255
    # 灰底
    # retpic = np.ones((150+mar*4, (n+1)*(w + mar*2), 3), np.uint8) * 128

    # 加载原始图
    if not source is None:
        source = cv2.cvtColor(source, cv2.COLOR_RGB2BGR)
        tmp = cv2.resize(source, (w, h))
        retpic[mar:h + mar, mar:w + mar, :] = tmp
        retpic = cv2ImgAddText(retpic, "原始图", mar, h+mar*2, font_color, fsize)

    # 加载结果图
    for i in range(n):
        try:
            image = Image.open(results[i])
            #img = cv2.cvtColor(np.asarray(image), cv2.COLOR_RGB2BGR)
            img = np.asarray(image)
            img = cv2.resize(img, (w, h))
            retpic[mar:h + mar, (i+1)*(w+mar*2)+mar:(i+2)*(w+mar*2)-mar, :] = img
        except Exception as e:
            pass

        # 写标题文字
        retpic = cv2ImgAddText(retpic, titles[i], (i+1)*(w+mar*2)+mar, h+mar*2, font_color, fsize)

    # 显示图
    if showimg==1:
        cv2.imshow("result", retpic)

    # 返回合成的图像
    return retpic

def test_show_result():
    print('test_show_result...')
    # 显示结果
    sfile = './output0/夏美酱_柳侑绮/image_427_045_1.png'
    source = cv2.imdecode(np.fromfile(sfile, dtype=np.uint8),-1)
    results = [
                './output0/夏美酱_柳侑绮/image_427_038_0.png',
                './output0/夏美酱_柳侑绮/image_427_009_1.png',
                './output0/夏美酱_柳侑绮/image_427_035_0.png',
                './output0/夏美酱_柳侑绮/image_427_002_1.png',
                './output0/夏美酱_柳侑绮/image_427_042_0.png',
                ]
    titles = [
                '夏美酱_柳侑绮(0.95)',
                '夏美酱_柳侑绮(0.93)',
                '夏美酱_柳侑绮(0.92)',
                '夏美酱_柳侑绮(0.83)',
                '夏美酱_柳侑绮(0.81)',
                ]

    ret = show_result(source, results, titles)
    # base64测试
    b64 = array2base64(ret)
    print('base64:', len(b64))
    img = base64toarray(b64)
    print(type(img))
    cv2.imshow("result_b64", img)

    cv2.waitKey(0)
    cv2.destroyAllWindows()
    sys.exit()


if __name__ == '__main__':
    pass
    pic = './output/苍井空/image_2653_003_0.png'
    pic = r'./output/张美荧/image_119_015_0.png'
    # show_image(pic)
    # cv2.waitKey(0)
